//
// Created by TongHua on 2020/12/24.
//

#include "HttpConn.h"
#include <map>
#include "../Logger/Logger.h"
#include <fstream>

using std::map;

//定义http响应的一些状态信息
const char *ok_200_title = "OK";
const char *error_400_title = "Bad Request";
const char *error_400_form = "Your request has bad syntax or is inherently impossible to staisfy.\n";
const char *error_403_title = "Forbidden";
const char *error_403_form = "You do not have permission to get file form this server.\n";
const char *error_404_title = "Not Found";
const char *error_404_form = "The requested file was not found on this server.\n";
const char *error_500_title = "Internal Error";
const char *error_500_form = "There was an unusual problem serving the request file.\n";

char* doc_root = "/root/WebServer/root";

map<string,string> users;

/**
 * @brief Set the Noblocking object
 *
 * @param _fd 文件描述符
 * @return int 返回修改前的文件描述符状态
 */
int SetNoblocking(int _fd)
{
    int old_option = fcntl( _fd, F_GETFL );
    int new_option = old_option | O_NONBLOCK;
    fcntl( _fd, F_SETFL, new_option );
    return old_option;
}

/**
 * @brief 向epoll中添加对文件描述符的监听
 *
 * @param _epollfd
 * @param _fd
 */
void Addfd( int _epollfd, int _fd, bool _one_shot )
{
    epoll_event event;
    event.data.fd = _fd;
    event.events = EPOLLIN | EPOLLET;
    if ( _one_shot )
    {
        event.events |= EPOLLONESHOT;
    }
    epoll_ctl( _epollfd, EPOLL_CTL_ADD, _fd, &event );
    SetNoblocking( _fd );
}

/**
 * @brief 从epoll中移除监听并且关闭文件描述符
 *
 * @param _epollfd
 * @param _fd
 */
void Removefd(int _epollfd,int _fd)
{
    epoll_ctl(_epollfd,EPOLL_CTL_DEL,_fd,0);
    close(_fd);
}

/**
 * 重置事件为EPOLLONESHOT
 * @param _epollfd
 * @param _fd
 * @param _ev
 */
void Modfd(int _epollfd,int _fd,int _ev)
{
    epoll_event event;
    event.data.fd = _fd;
    event.events = _ev | EPOLLET | EPOLLONESHOT | EPOLLRDHUP;
    epoll_ctl(_epollfd,EPOLL_CTL_MOD,_fd,&event);
}

void HttpConn::InitMySQLResult(ConnectionPool *_connPool) {
    MYSQL* mysql = NULL;
    // 从数据池中获取一个数据连接
    ConnectionRALL connectionRall(&mysql, _connPool);

    // 查询表中的信息
    if ( mysql_query( mysql, "SELECT username,passwd FROM user" ) )
    {
        LOG_ERROR("SELECT error:%s\n",mysql_error(mysql));
    }

    // 查询完整的数据集
    MYSQL_RES *result = mysql_store_result(mysql);

    // 返回数据集中所有的列
    int num_fields = mysql_num_fields(result);

    // 获取所有字段的结构数组
    MYSQL_FIELD *fields = mysql_fetch_field( result );

    while ( MYSQL_ROW row = mysql_fetch_row(result) )
    {
        string name(row[0]);
        string passwd(row[1]);
        users[name] = passwd;
    }
}

/***
 * 关闭连接，并且客户端数量减一
 * @param _real_close
 */
void HttpConn::CloseConnect(bool _real_close) {
    if( _real_close && ( m_sockfd != -1) )
    {
        Removefd( m_epollfd , m_sockfd );
        m_sockfd = -1;
        m_user_count--;
    }
}

int HttpConn::m_user_count = 0;
int HttpConn::m_epollfd = -1;

/***
 * 初始化链接
 * @param _sockfd
 * @param _address
 */
void HttpConn::Init(int _sockfd, const sockaddr_in &_address) {
    m_sockfd = _sockfd;
    m_address = _address;
    Addfd( m_epollfd, m_sockfd, true );
    m_user_count++;
    Init();
}

/***
 * 类成员初始化
 * m_check_state 为状态机默认状态
 */
void HttpConn::Init() {
    m_check_state = CHECK_STATE_REQUESTLINE;
    m_linger = false;
    m_medtod = GET;
    m_url = 0;
    m_version = 0;
    m_content_length = 0;
    m_host = 0;
    m_start_line = 0;
    m_checked_idx = 0;
    m_read_idx = 0;
    m_write_idx = 0;
    m_cgi = 0;
    memset(m_read_buf,'\0',READ_BUFFER_SIZE);
    memset(m_write_buf,'\0',WRITE_BUFFER_SIZE);
    memset(m_real_file,'\0',FILENAME_LEN);
}

/**
 * 状态机，用于解析http每一行信息
 * @return 返回读取状态，有LINE_OK,LINE_BAD,LINE_OPEN
 */
HttpConn::LINE_STATUS HttpConn::ParseLine() {
    char temp;
    for (; m_checked_idx < m_read_idx ; ++m_checked_idx) {
        temp = m_read_buf[ m_checked_idx ];
        if ( temp == '\r' )
        {
            if ( ( m_checked_idx + 1 ) == m_read_idx )
                return LINE_OPEN;
            else if ( m_read_buf[m_checked_idx + 1] == '\n' )
            {
                m_read_buf[m_checked_idx++] = '\0';
                m_read_buf[m_checked_idx++] = '\0';
                return LINE_OK;
            }
            return LINE_BAD;
        }else if ( temp == '\n' )
        {
            if ( m_checked_idx > 1 && m_read_buf[ m_checked_idx - 1 ] == '\r' ){
                m_read_buf[m_checked_idx - 1] = '\0';
                m_read_buf[m_checked_idx ++] = '\0';
                return LINE_OK;
            }
            return LINE_BAD;
        }
    }
    return LINE_OPEN;
}

/**
 * 循环读取数据
 * @return
 */
bool HttpConn::ReadOnce() {
    if ( m_read_idx >= READ_BUFFER_SIZE )
        return false;
    int byte_read = 0;
    while ( true )
    {
        byte_read = recv( m_sockfd, m_read_buf + m_read_idx, READ_BUFFER_SIZE - m_read_idx, 0 );
        if ( byte_read == -1 )
        {
            if ( errno == EAGAIN || errno == EWOULDBLOCK )
                break;
            return false;
        }else if ( byte_read == 0 )
        {
            return false;
        }
        m_read_idx += byte_read;
    }
    return true;
}

/**
 * 解析http请求行，获得请求方法，目标url及http版本号
 * @param _text
 * @return
 */
HttpConn::HTTP_CODE HttpConn::ParseRequestLine(char *_text) {
    m_url = strpbrk( _text, " \t" );
    if ( !m_url )
        return BAD_REQUEST;
    *m_url++ = '\0';
    char* method = _text;
    if ( strcasecmp( method, "GET" ) == 0 )
    {
        m_medtod = GET;
    } else if ( strcasecmp( method, "POST" ) == 0 )
    {
        m_medtod = POST;
        m_cgi = 1;
    } else {
        return BAD_REQUEST;
    }
    m_url += strspn( m_url, " \t" );
    m_version = strpbrk( m_url, " \t" );
    if ( !m_version )
        return BAD_REQUEST;
    *m_version++ = '\0';
    m_version += strspn( m_version, " \t" );
    if ( strcasecmp( m_version, "HTTP/1.1" ) != 0 )
    {
        return BAD_REQUEST;
    }
    if ( strncasecmp( m_url, "http://", 7 ) == 0 )
    {
        m_url += 7;
        m_url = strchr( m_url, '/' );
    }

    if ( strncasecmp( m_url, "https://", 8 ) == 0 )
    {
        m_url += 8;
        m_url = strchr( m_url, '/' );
    }

    if (!m_url || m_url[0] != '/')
        return BAD_REQUEST;
    //当url为/时，显示判断界面
    if (strlen(m_url) == 1)
        strcat(m_url, "judge.html");
    m_check_state = CHECK_STATE_HEADER;
    return NO_REQUEST;
}

/**
 * 解析Http头部信息
 * @param _text
 * @return
 */
HttpConn::HTTP_CODE HttpConn::ParseHeaders(char *_text) {
    if ( _text[0] == '\0' )
    {
        if ( m_content_length != 0 )
        {
            m_check_state = CHECK_STATE_CONTENT;
            return NO_REQUEST;
        }
        return GET_REQUEST;
    }else if ( strncasecmp( _text, "Connection:", 11 ) == 0 )   // 比较俩个字符串，相同 = 0，s1长度大于s2长度则返回大于0 的值，s1 长度若小于s2 长度则返回小于0的值
    {
        _text += 11;
        _text += strspn( _text, " \t" );     //  检索字符串 str1 中第一个不在字符串 str2 中出现的字符下标。
        if ( strcasecmp( _text, "keep-alive" ) == 0 )
        {
            m_linger = true;
        }
    }else if ( strncasecmp( _text, "Content-length:", 15 ) == 0 )
    {
        _text += 15;
        _text += strspn( _text, " \t" );
        m_content_length = atoi( _text );
    }else if ( strncasecmp( _text, "Host:", 5 ) == 0 )
    {
        _text += 5;
        _text += strspn( _text, " \t" );
        m_host = _text;
    }else
    {
        //LOG_INFO( "oop!unknow header: %s\n",_text );
        //Logger::Instance()->Flush();
        //printf("oop!unknow header: %s\n",_text);
    }
    return NO_REQUEST;
}

/**
 * 判断http请求是否被完整读入
 * @param _text
 * @return
 */
HttpConn::HTTP_CODE HttpConn::ParseContent(char *_text) {
    if ( m_read_idx >= ( m_content_length + m_checked_idx ) )
    {
        _text[m_content_length] = '\0';
        m_string = _text;
        return GET_REQUEST;
    }
    return NO_REQUEST;
}

/**
 * 循环解析每一行数据
 * @return
 */
HttpConn::HTTP_CODE HttpConn::ProcessRead() {
    LINE_STATUS line_state = LINE_OK;
    HTTP_CODE ret = NO_REQUEST;
    char* text = 0;

    while ( ( m_check_state == CHECK_STATE_CONTENT && line_state == LINE_OK ) || ( ( line_state = ParseLine() ) == LINE_OK ) )
    {
        text = GetLine();
        m_start_line = m_checked_idx;
        switch ( m_check_state ) {
            case CHECK_STATE_REQUESTLINE:
                ret = ParseRequestLine( text );
                if ( ret == BAD_REQUEST )
                    return BAD_REQUEST;
                break;
            case CHECK_STATE_HEADER:
                ret = ParseHeaders( text );
                if ( ret == BAD_REQUEST )
                    return BAD_REQUEST;
                else if ( ret == GET_REQUEST )
                    return DoRequest();
                break;
            case CHECK_STATE_CONTENT:
                ret = ParseContent( text );
                if ( ret == GET_REQUEST )
                    return DoRequest();
                line_state = LINE_OPEN;
                break;
            default:
                return INTERNAL_ERROR;
        }
    }
    return NO_REQUEST;
}

/**
 * 执行请求
 * @return
 */
HttpConn::HTTP_CODE HttpConn::DoRequest() {
    strcpy( m_real_file, doc_root );
    int len = strlen( doc_root );
    const char* p = strrchr( m_url, '/' );
    printf( "%s\n", m_url );
    if ( m_cgi == 1 && ( ( *( p + 1 ) == '2' ) || ( *( p + 1 ) == '3' ) ) )
    {
        char msg = m_url[1];

        char* url_real = ( char* )malloc( sizeof(char) * 200 );
        strcpy( url_real, "/" );
        strcat( url_real, m_url + 2 );

        char name[100],passwd[100];
        int i = 0;
        for ( i = 5 ;m_string[i] != '&'; ++i )
        {
            name[ i - 5 ] = m_string[i];
        }
        name[i - 5] = '\0';
        int j = 0;
        for ( i = i + 10; m_string[i] != '\0'; ++i, ++j )
        {
            passwd[j] = m_string[i];
        }
        passwd[j] = '\0';
        if ( *( p + 1 ) == '3' )
        {
            char *sql_insert = (char *)malloc(sizeof(char) * 200);
            strcpy(sql_insert, "INSERT INTO user(username, passwd) VALUES(");
            strcat(sql_insert, "'");
            strcat(sql_insert, name);
            strcat(sql_insert, "', '");
            strcat(sql_insert, passwd);
            strcat(sql_insert, "')");

            if (users.find(name) == users.end())
            {
                m_lock.Lock();
                int res = mysql_query(mysql, sql_insert);
                users.insert(std::pair<string, string>(name, passwd));
                m_lock.UnLock();

                if (!res)
                    strcpy(m_url, "/log.html");
                else
                    strcpy(m_url, "/registerError.html");
            }
            else
                strcpy(m_url, "/registerError.html");
        }else if ( *( p + 1 ) == '2' )
        {
            if ( users.find(name) != users.end() && users[name] == passwd )
            {
                strcpy(m_url, "/welcome.html");
            }else
            {
                strcpy(m_url, "/logError.html");
            }
            //printf("Login\n");
        }
    }
    if ( *(p+1) == '1' )
    {
        char* url_real = (char*)malloc( sizeof(char) * 200 );
        strcpy( url_real, "/log.html" );
        strncpy( m_real_file + len,url_real, strlen( url_real ) );
        free( url_real );
    }else if ( *(p+1) == '0' )
    {
        char* url_real = (char*)malloc( sizeof( char ) * 200 );
        strcpy( url_real, "/register.html" );
        strncpy( m_real_file+len, url_real, FILENAME_LEN - len - 1 );
        free( url_real );
    }else
    {
        strncpy( m_real_file + len, m_url, FILENAME_LEN - len -1 );
    }

    if ( stat( m_real_file, &m_file_stat ) < 0 )
    {
        return NO_RESOURCE;
    }
    if ( ! ( m_file_stat.st_mode & S_IROTH ) )
    {
        return FORBIDDEN_REQUEST;
    }
    if ( S_ISDIR( m_file_stat.st_mode ) )
    {
        return BAD_REQUEST;
    }
    int fd = open( m_real_file, O_RDONLY );
    m_file_address = (char*)mmap( 0, m_file_stat.st_size, PROT_READ, MAP_PRIVATE, fd, 0 );
    close( fd );
    return FILE_REQUEST;
}

/**
 * 文件映射
 */
void HttpConn::Unmap() {
    if ( m_file_address )
    {
        munmap( m_file_address, m_file_stat.st_size );
        m_file_address = 0;
    }
}


/**
 * 写入Http响应
 * @return
 */
bool HttpConn::Write() {
    int temp = 0;
    int byte_have_send = 0;
    int bytes_to_send = m_write_idx;
    if ( bytes_to_send == 0 )
    {
        Modfd( m_epollfd, m_sockfd, EPOLLIN );
        Init();
        return true;
    }

    while ( 1 )
    {
        temp = writev( m_sockfd, m_iv, m_iv_count );
        if ( temp <= -1 )
        {
            if ( errno ==  EAGAIN )
            {
                Modfd( m_epollfd, m_sockfd, EPOLLOUT );
                return true;
            }
            Unmap();
            return false;
        }

        bytes_to_send -= temp;
        byte_have_send += temp;
        if ( bytes_to_send <= byte_have_send )
        {
            Unmap();
            if ( m_linger )
            {
                Init();
                Modfd( m_epollfd, m_sockfd, EPOLLIN );
                return true;
            }else{
                Modfd( m_epollfd, m_sockfd, EPOLLIN );
                return false;
            }
        }
    }
}

/**
 * 向写缓冲中写入待发送的数据
 * @param _format
 * @param ...
 * @return
 */
bool HttpConn::AddResponse(const char *_format, ...) {
    if ( m_write_idx >= WRITE_BUFFER_SIZE )
    {
        return false;
    }
    va_list  arg_list;
    va_start( arg_list, _format );
    int len = vsnprintf( m_write_buf + m_write_idx, WRITE_BUFFER_SIZE - 1 - m_write_idx, _format, arg_list );
    if ( len >= ( WRITE_BUFFER_SIZE - 1 - m_write_idx ) )
    {
        return false;
    }
    m_write_idx += len;
    va_end( arg_list );
    return true;
}

bool HttpConn::AddStatusLine(int _status, const char *_title) {
    return AddResponse( "%s %d %s\r\n", "HTTP/1.1", _status, _title );
}

bool HttpConn::AddHeaders(int _content_length) {
    AddContentLenght( _content_length );
    AddLinger();
    AddBlankLine();
}

bool HttpConn::AddContentLenght(int _content_length) {
    return AddResponse( "Content-Length:%d\\r\\n", _content_length );
}

bool HttpConn::AddLinger() {
    return AddResponse("Connection:%s\r\n", (m_linger == true) ? "keep-alive" : "close");
}

bool HttpConn::AddBlankLine() {
    return AddResponse( "%s", "\r\n" );
}

bool HttpConn::AddContent(const char *_content) {
    return AddResponse( "%s", _content );
}

bool HttpConn::ProcessWrite(HTTP_CODE _ret) {
    switch ( _ret ) {
        case INTERNAL_ERROR:
            AddStatusLine(500, error_500_title);
            AddHeaders(strlen(error_500_form));
            if (!AddContent(error_500_form)) {
                return false;
            }
            break;
        case BAD_REQUEST:
            AddStatusLine(400, error_400_title);
            AddHeaders(strlen(error_400_form));
            if ( ! AddContent( error_400_form ) )
            {
                return false;
            }
            break;
        case NO_RESOURCE:
            AddStatusLine(404, error_404_title);
            AddHeaders(strlen(error_404_form));
            if ( ! AddContent( error_404_form ) )
            {
                return false;
            }
            break;
        case FORBIDDEN_REQUEST:
            AddStatusLine(403, error_403_title);
            AddHeaders(strlen(error_403_form));
            if ( ! AddContent( error_403_form ) )
            {
                return false;
            }
            break;
        case FILE_REQUEST:
            AddStatusLine( 200, ok_200_title );
            if ( m_file_stat.st_size != 0 )
            {
                AddHeaders( m_file_stat.st_size );
                m_iv[ 0 ].iov_base = m_write_buf;
                m_iv[ 0 ].iov_len = m_write_idx;
                m_iv[ 1 ].iov_base = m_file_address;
                m_iv[ 1 ].iov_len = m_file_stat.st_size;
                m_iv_count = 2;
                return true;
            }else{
                const char* ok_string = "<html><body></body></html>";
                AddHeaders( strlen( ok_string ) );
                if ( ! AddContent( ok_string ) )
                {
                    return false;
                }
            }
            break;
        default:
            break;
    }
    m_iv[ 0 ].iov_base = m_write_buf;
    m_iv[ 0 ].iov_len = m_write_idx;
    m_iv_count = 1;
    return true;
}

void HttpConn::Process() {
    HTTP_CODE read_ret = ProcessRead();
    if ( read_ret == NO_RESOURCE )
    {
        Modfd( m_epollfd, m_sockfd, EPOLLIN );
        return;
    }
    bool write_ret = ProcessWrite( read_ret );
    if ( !write_ret )
    {
        CloseConnect();
    }
    Modfd( m_epollfd, m_sockfd, EPOLLOUT );
}